import datetime
import os
import tkinter as tk
from tkinter import filedialog, messagebox, ttk

import numpy as np
from PIL import Image, ImageTk
import cv2
from ultralytics import cfg
from UI.ui.menu import MenuBar
from UI.ui.predict_frame import PredictionFrame
from UI.utils.predict import PredictionParameters, BatchPredictor
from UI.ui.train import YOLOTrainingGUI
import threading
import socket
import threading
import json

class MainWindow:
    def __init__(self, root, menu_bar=None):
        self.root = root
        self.root.title("AI 检测系统")
        self.root.geometry("900x700")
        self.root.config(background="white")

        self.predictor = None
        self.server_connected = False
        self.video_detection_running = False
        # 视频/摄像头相关变量
        self.cap = None
        self.video_running = False
        self.camera_running = False
        # 参数管理器
        self.params_manager = PredictionParameters()
        # 创建菜单栏
        self.model_path = tk.StringVar()


        # 先创建菜单栏
        if menu_bar is None:
            from UI.ui.menu import MenuBar
            self.menu_bar = MenuBar(root, self,trainer=None)  # 注意这里传入了 self
        else:
            self.menu_bar = menu_bar

        self.load_recent_files()
        self.create_recent_files_menu()

        # 然后创建输入源选择区
        self.create_input_section()

        # 初始化时更新模型下拉框
        self.root.after(100, self.update_model_dropdown)  # 稍微延迟执行以确保界面已创建
        self.video_paused = False

        # 创建检测结果显示区
        self.create_detection_result_section()
        self.result_server = None
        self.result_server_thread = None
        self.start_result_server()  # 启动结果接收服务

        self.recent_files_path = "recent_files.json"  # 存储最近文件的 JSON 文件
        self.max_recent_files = 5
        self.recent_files = []  # 存储最近文件的列表，按时间倒序（最新在前）

        # 添加批处理结果管理
        self.batch_results = []  # 存储批处理结果
        self.current_result_index = -1  # 当前显示的结果索引

        # 添加翻页控件引用
        self.prev_button = None
        self.next_button = None
        self.page_label = None
        self.navigation_frame = None

        self.original_video_path = None
        # 添加鼠标滚轮事件绑定
        self.canvas_original.bind("<MouseWheel>", self.on_mouse_wheel_original)
        self.canvas_detected.bind("<MouseWheel>", self.on_mouse_wheel_detected)


    

        # 添加批处理结果管理
        self.batch_results = []  # 存储批处理结果
        self.current_result_index = -1  # 当前显示的结果索引

        # 添加翻页控件引用
        self.prev_button = None
        self.next_button = None
        self.page_label = None
        self.navigation_frame = None

        self.model_path = tk.StringVar()
        self.original_video_path = None
        # 添加鼠标滚轮事件绑定
        self.canvas_original.bind("<MouseWheel>", self.on_mouse_wheel_original)
        self.canvas_detected.bind("<MouseWheel>", self.on_mouse_wheel_detected)

    def create_input_section(self):
        frame_input = tk.Frame(self.root, bg="white")
        frame_input.pack(pady=10, padx=10, fill=tk.X)

        tk.Button(frame_input, text="打开图片", command=self.open_image,bg="#4CAF50",
            fg="white",
            font=('微软雅黑', 10, 'bold'),
            padx=15,
            pady=5
        ).pack(side=tk.LEFT, padx=5)
        tk.Button(frame_input, text="打开视频", command=self.open_video,bg="#4CAF50",
            fg="white",
            font=('微软雅黑', 10, 'bold'),
            padx=15,
            pady=5).pack(side=tk.LEFT, padx=5)
        tk.Button(frame_input, text="启动摄像头", command=self.open_camera,bg="#4CAF50",
            fg="white",
            font=('微软雅黑', 10, 'bold'),
            padx=15,
            pady=5).pack(side=tk.LEFT, padx=5)

        # 输入框 + 标签
        tk.Label(frame_input, text="路径：", bg="white").pack(side=tk.LEFT, padx=(20, 5))
        # 缩短路径输入框宽度
        self.path_entry = tk.Entry(frame_input, width=30)
        self.path_entry.pack(side=tk.LEFT, fill=tk.X, expand=True, padx=5)

        # 浏览按钮
        tk.Button(frame_input, text="浏览", command=self.browse_file).pack(side=tk.LEFT, padx=5)

    def create_detection_result_section(self):
        frame_results = tk.Frame(self.root, bg="white")
        frame_results.pack(pady=10, padx=10, fill=tk.BOTH, expand=True)

        # 左侧原始图像/视频帧显示区域
        frame_left = tk.Frame(frame_results, bg="white")
        frame_left.grid(row=0, column=0, padx=5, pady=5, sticky="nsew")

        self.canvas_original_label = tk.Label(frame_left, text="原始文件", bg="white")
        self.canvas_original_label.pack()
        self.canvas_original = tk.Canvas(frame_left, bg="#DCDCDC", width=300, height=300)
        self.canvas_original.pack(fill=tk.BOTH, expand=True)

        # 右侧检测结果显示区域
        frame_right = tk.Frame(frame_results, bg="white")
        frame_right.grid(row=0, column=1, padx=5, pady=5, sticky="nsew")

        self.canvas_detected_label = tk.Label(frame_right, text="检测结果", bg="white")
        self.canvas_detected_label.pack()
        self.canvas_detected = tk.Canvas(frame_right, bg="#DCDCDC", width=300, height=300)
        self.canvas_detected.pack(fill=tk.BOTH, expand=True)

        # 添加分隔线 - 修改为跨越多行以增加长度
        separator = tk.Frame(frame_results, width=1, bg="gray")
        separator.grid(row=0, column=2, rowspan=2, padx=5, pady=5, sticky="ns")

        # 添加控制按钮区域
        frame_controls = tk.Frame(frame_results, bg="white")
        frame_controls.grid(row=0, column=3, padx=5, pady=5, sticky="nsew")

        # 控制按钮使用grid布局以便更好地控制
        # 模型选择下拉框
        tk.Label(frame_controls, text="模型选择:", bg="white", font=("微软雅黑", 10)).grid(row=0, column=0, pady=5,
                                                                                       sticky="w")

        # 创建模型下拉框
        self.model_dropdown = ttk.Combobox(
            frame_controls,
            textvariable=self.model_path,
            state="readonly",
            width=15,
            font=("微软雅黑", 10)
        )
        self.model_dropdown.grid(row=1, column=0, pady=5, sticky="ew")

        # 调整参数按钮（注意行号从3开始）
        tk.Button(frame_controls, text="调整参数", command=self.open_parameter_window, width=15, height=2,bg="#4CAF50",
            fg="white",
            font=('微软雅黑', 10, 'bold'),
            padx=15,
            pady=5).grid(row=3,column=0,pady=5,sticky="ew")
        tk.Button(frame_controls, text="开始检测", command=self.start_detection, width=15, height=2,bg="#4CAF50",
            fg="white",
            font=('微软雅黑', 10, 'bold'),
            padx=15,
            pady=5).grid(row=4,column=0,pady=5,sticky="ew")
        tk.Button(frame_controls, text="暂停检测", command=self.pause_detection, width=15, height=2,bg="#2196F3",
            fg="white",
            font=('微软雅黑', 10, 'bold'),
            padx=15,
            pady=5).grid(row=5,column=0,pady=5,sticky="ew")
        tk.Button(frame_controls, text="停止检测", command=self.stop_detection, width=15, height=2,bg="#2196F3",
            fg="white",
            font=('微软雅黑', 10, 'bold'),
            padx=15,
            pady=5).grid(row=6,column=0,pady=5,sticky="ew")
        tk.Button(frame_controls, text="保存结果", command=self.save_detection, width=15, height=2,bg="#FF9800",
            fg="white",
            font=('微软雅黑', 10, 'bold'),
            padx=15,
            pady=5).grid(row=7,column=0,pady=5,sticky="ew")

        # 配置控制按钮列权重
        frame_controls.grid_columnconfigure(0, weight=1)

        # 文本框显示检测到的目标类别及数量和检测日志
        self.text_results = tk.Text(
            frame_results,
            wrap=tk.WORD,
            width=40,
            height=10,
            font=("Segoe UI", 9)  # 关键：设置支持 emoji 的字体
        )
        # 修改为只跨越前两列（与图像显示区域同宽）
        self.text_results.grid(row=1, column=0, columnspan=2, padx=5, pady=5, sticky="nsew")

        # 设置网格布局权重，使组件能够随窗口大小变化而调整
        frame_results.grid_rowconfigure(0, weight=1)
        frame_results.grid_rowconfigure(1, weight=0)  # 日志区域可以调整高度
        frame_results.grid_columnconfigure(0, weight=1)
        frame_results.grid_columnconfigure(1, weight=1)
        frame_results.grid_columnconfigure(2, weight=0)  # 分隔线不扩展
        frame_results.grid_columnconfigure(3, weight=0)  # 控制按钮区域不扩展

        # 配置文本标签样式
        self.text_results.tag_configure("highlight", foreground="red", font=("Segoe UI", 10, "bold"))
        self.text_results.tag_configure("class_label", foreground="blue", font=("Segoe UI", 9))
        self.text_results.tag_configure("class_count_highlight", foreground="green", font=("Segoe UI", 9, "bold"))
        self.text_results.tag_configure("class_count", foreground="gray", font=("Segoe UI", 9))
        self.text_results.tag_configure("error", foreground="red", font=("Segoe UI", 9, "bold"))
        self.text_results.tag_configure("title", foreground="darkblue", font=("Segoe UI", 10, "bold"))

    def create_navigation_buttons(self):
        """创建翻页按钮"""
        # 如果已经存在翻页控件，先销毁
        if hasattr(self, 'navigation_frame') and self.navigation_frame:
            self.navigation_frame.destroy()

        # 创建翻页控件区域 - 使用pack布局
        self.navigation_frame = tk.Frame(self.root, bg="white")
        self.navigation_frame.pack(pady=5, padx=10, fill=tk.X)

        # 翻页按钮 - 使用pack布局
        self.prev_button = tk.Button(self.navigation_frame, text="上一页", command=self.show_prev_result, width=10)
        self.prev_button.pack(side=tk.LEFT, padx=5)

        self.next_button = tk.Button(self.navigation_frame, text="下一页", command=self.show_next_result, width=10)
        self.next_button.pack(side=tk.LEFT, padx=5)

        # 当前页码显示
        self.page_label = tk.Label(self.navigation_frame, text="0/0", bg="white", font=("微软雅黑", 10))
        self.page_label.pack(side=tk.LEFT, padx=5)

        # 初始化按钮状态
        self.update_navigation_buttons()

    def hide_navigation_buttons(self):
        """隐藏翻页按钮"""
        if self.navigation_frame:
            self.navigation_frame.destroy()
            self.navigation_frame = None
            self.prev_button = None
            self.next_button = None
            self.page_label = None

    def update_navigation_buttons(self):
        """更新翻页按钮状态"""
        if not hasattr(self, 'prev_button') or not hasattr(self, 'next_button') or not hasattr(self, 'page_label'):
            return

        total_results = len(self.batch_results)
        current_index = self.current_result_index + 1 if self.current_result_index >= 0 else 0

        # 更新页码显示
        self.page_label.config(text=f"{current_index}/{total_results}")

        # 更新按钮状态
        self.prev_button.config(state=tk.NORMAL if self.current_result_index > 0 else tk.DISABLED)
        self.next_button.config(
            state=tk.NORMAL if self.current_result_index < total_results - 1 and total_results > 0 else tk.DISABLED)

    def show_prev_result(self):
        """显示上一个结果"""
        if self.current_result_index > 0:
            self.current_result_index -= 1
            self.display_current_result()
            self.update_navigation_buttons()

    def show_next_result(self):
        """显示下一个结果"""
        if self.current_result_index < len(self.batch_results) - 1:
            self.current_result_index += 1
            self.display_current_result()
            self.update_navigation_buttons()

    def display_current_result(self):
        """显示当前结果"""
        if 0 <= self.current_result_index < len(self.batch_results):
            result_data = self.batch_results[self.current_result_index]

            # 清空文本框并显示当前结果
            if result_data.get('status') == 'success':
                image_path = result_data['image_path']
                total_objects = result_data['total_objects']
                class_counts = result_data['class_counts']

                # 清空文本框
                self.text_results.delete(1.0, tk.END)

                # 创建标题部分
                title_text = f"\n{'='*50}\n"
                title_text += "📊 检测结果报告\n"
                title_text += f"{'='*50}\n"
                self.text_results.insert(tk.END, title_text)

                # 显示图片路径
                self.text_results.insert(tk.END, "📁 图片路径: ")
                self.text_results.insert(tk.END, f"{image_path}\n")

                                # 显示总目标数（高亮）
                self.text_results.insert(tk.END, "🎯 总目标数: ")
                self.text_results.insert(tk.END, f"{total_objects}\n", "highlight")  # 这里正确应用了标签

                # 显示各类别数量
                self.text_results.insert(tk.END, "\n📋 各类别数量:\n", "title")  # 应用title标签
                self.text_results.insert(tk.END, "-" * 30 + "\n")
                # 检查是否检测到火灾
                fire_count = 0

                # 在类别显示部分
                for class_name, count in class_counts.items():
                    # 类别名称（带图标）
                    self.text_results.insert(tk.END, f"  {class_name}: ", "class_label")  # 应用class_label标签
                    # 数量（如果大于0则高亮显示）
                    if count > 0:
                        # 使用星星图标表示数量
                        stars = "⭐" * min(count, 5)  # 最多显示5个星星
                        self.text_results.insert(tk.END, f"{stars} {count}\n",
                                                 "class_count_highlight")  # 应用class_count_highlight标签
                        # 检查是否检测到火灾
                        if class_name.lower() == 'fire':
                            fire_count = count
                    else:
                        self.text_results.insert(tk.END, f"{count}\n", "class_count")  # 应用class_count标签

                # 如果检测到火灾，添加警告信息
                if fire_count > 0:
                    self.text_results.insert(tk.END, f"⚠️ 警告：发现{fire_count}处火灾！\n", "error")
                    # 弹出警告框提醒用户
                    self.root.after(100,
                                    lambda: messagebox.showwarning("火灾警报", f"检测到{fire_count}处火灾！请立即处理！"))

                self.text_results.insert(tk.END, "-" * 30 + "\n")
                self.text_results.insert(tk.END, f"{'='*50}\n\n")

                # 显示预测结果图片
                result_image_path = result_data.get('result_image_path')
                if result_image_path and os.path.exists(result_image_path):
                    self.display_result_image(result_image_path)
                else:
                    # 如果没有保存的预测图片，尝试显示原始图片
                    self.display_result_image(image_path)

                # 同时显示原始图片
                self.display_image(image_path)

            else:
                self.text_results.delete(1.0, tk.END)  # 清空文本框
                self.text_results.insert(tk.END, "❌ 处理失败: ")
                self.text_results.insert(tk.END, f"{result_data.get('error', '未知错误')}\n", "error")

            self.text_results.see(tk.END)
            self.text_results.update_idletasks()


    def start_result_server(self):
        """启动结果接收服务器"""
        self.result_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.result_server.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

        try:
            self.result_server.bind(('localhost', 12346))
            self.result_server.listen(1)
            print("结果接收服务器已启动，监听端口 12346")

            # 在后台线程中监听连接
            self.result_server_thread = threading.Thread(target=self.listen_for_results)
            self.result_server_thread.daemon = True
            self.result_server_thread.start()

        except Exception as e:
            print(f"启动结果服务器失败: {e}")

    def listen_for_results(self):
        """监听来自预测器的结果"""
        try:
            while True:
                client_socket, address = self.result_server.accept()
                print(f"预测器连接: {address}")

                # 为每个连接创建处理线程
                client_thread = threading.Thread(
                    target=self.handle_predictor_client,
                    args=(client_socket,)
                )
                client_thread.daemon = True
                client_thread.start()

        except Exception as e:
            print(f"监听结果时出错: {e}")

    def handle_predictor_client(self, client_socket):
        """处理来自预测器的客户端连接"""
        try:
            buffer = ""
            while True:
                data = client_socket.recv(4096)
                if not data:
                    break

                # 将接收到的数据添加到缓冲区
                buffer += data.decode('utf-8')

                # 处理可能的不完整JSON消息
                messages = buffer.split('\n')
                buffer = messages[-1]  # 保留最后一个可能不完整的消息

                for message in messages[:-1]:
                    if message.strip():
                        try:
                            result_data = json.loads(message)
                            # 在主线程中更新UI
                            self.root.after(0, self.update_ui_with_result, result_data)
                        except json.JSONDecodeError:
                            print(f"无法解析JSON: {message[:100]}...")  # 只打印前100个字符
                            # 创建一个错误消息来显示问题
                            error_result = {
                                "status": "error",
                                "error": "数据格式错误，无法解析结果"
                            }
                            self.root.after(0, self.update_ui_with_result, error_result)
        except Exception as e:
            print(f"处理预测器客户端时出错: {e}")
            # 发送错误信息到UI
            error_result = {
                "status": "error",
                "error": f"客户端连接错误: {str(e)}"
            }
            self.root.after(0, self.update_ui_with_result, error_result)
        finally:
            client_socket.close()








    def display_result_image(self, image_path):
        """在右侧画布显示预测结果图片或视频第一帧"""
        try:
            # 判断是否为视频文件
            if image_path.lower().endswith(('.mp4', '.avi', '.mov', '.wmv', '.mkv')):
                # 使用 OpenCV 读取视频的第一帧
                cap = cv2.VideoCapture(image_path)
                if not cap.isOpened():
                    raise Exception("无法打开视频文件")

                ret, frame = cap.read()
                cap.release()

                if ret:
                    # 将 OpenCV 图像转换为 PIL 图像
                    frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                    image = Image.fromarray(frame_rgb)
                    self.display_image_on_canvas(image, self.canvas_detected)
                    self.canvas_detected_label.config(text=f"检测结果: {os.path.basename(image_path)}")
                else:
                    raise Exception("无法读取视频帧")
            else:
                # 处理普通图片
                image = Image.open(image_path)
                self.display_image_on_canvas(image, self.canvas_detected)
                self.canvas_detected_label.config(text=f"检测结果: {os.path.basename(image_path)}")
                self.current_image = image.copy()
        except Exception as e:
            print(f"无法显示结果图片/视频: {str(e)}")
            self.canvas_detected_label.config(text="检测结果 (显示失败)")





#预测函数=============
    def update_model_dropdown(self):
        """更新模型下拉框内容"""
        try:
            # 获取当前文件所在目录
            current_dir = os.path.dirname(os.path.abspath(__file__))
            # 向上两级找到项目根目录（因为当前在 UI/ui/ 下）
            project_root = os.path.dirname(os.path.dirname(current_dir))

            # 构建配置文件路径
            model_search_path = os.path.join(project_root, 'results')
            # 检查路径是否存在
            if not os.path.exists(model_search_path):
                messagebox.showerror("错误", f"结果目录不存在: {model_search_path}")
                return

            # 搜索所有best.pt文件
            model_files = []

            # 递归搜索results目录下的所有best.pt文件
            for root, dirs, files in os.walk(model_search_path):
                for file in files:
                    if file == "best.pt":
                        # 获取相对路径，去掉results/前缀
                        relative_path = os.path.relpath(root, model_search_path)
                        # 如果是根目录，显示为"results"
                        if relative_path == ".":
                            relative_path = "results"
                        # 组合显示名称
                        display_name = f"{relative_path}/best.pt"
                        full_path = os.path.join(root, file)
                        model_files.append((display_name, full_path))

            # 如果没有找到模型文件，添加默认选项
            if not model_files:
                model_files = [("未找到模型文件", "未找到模型文件")]
                self.model_path.set("未找到模型文件")
            else:
                # 设置默认选中第一个模型
                self.model_path.set(model_files[0][0])

            # 更新下拉框选项，只显示简化名称
            self.model_dropdown['values'] = [item[0] for item in model_files]

            # 存储完整的路径映射，用于后续获取实际路径
            self.model_full_paths = {item[0]: item[1] for item in model_files}

            # 显示调试信息
            print(f"找到的模型文件: {[item[0] for item in model_files]}")

        except Exception as e:
            error_msg = f"更新模型列表时发生错误: {str(e)}"
            print(error_msg)
            messagebox.showerror("错误", error_msg)

    def get_selected_model(self):
        """获取当前选中的模型路径"""
        selected_display_name = self.model_path.get()
        if selected_display_name == "未找到模型文件":
            return None
        # 返回完整的文件路径
        return self.model_full_paths.get(selected_display_name)

    def open_parameter_window(self):
        """打开参数设置窗口"""
        # 隐藏主窗口
        self.root.withdraw()

        # 创建参数设置窗口
        self.param_window = tk.Toplevel(self.root)
        self.param_window.title("预测参数设置")
        self.param_window.geometry("600x500")
        self.param_window.config(background="white")

        # 设置窗口关闭事件
        self.param_window.protocol("WM_DELETE_WINDOW", self.on_param_window_close)

        # 创建预测参数框架
        self.prediction_frame = PredictionFrame(self.param_window, self.params_manager)

        # 重写保存按钮的命令
        self.prediction_frame.save_button.config(command=self.save_and_close_param_window)

        # 重写重置按钮的命令
        self.prediction_frame.reset_button.config(command=self.prediction_frame.reset_defaults)

    def save_and_close_param_window(self):
        """保存参数并关闭参数窗口"""
        try:
            # 调用原来的保存方法
            self.prediction_frame.save_config()

            # 关闭参数窗口并显示主窗口
            self.param_window.destroy()
            self.root.deiconify()
        except Exception as e:
            messagebox.showerror("错误", f"保存参数时发生错误: {str(e)}")

    def on_param_window_close(self):
        """参数窗口关闭事件处理"""
        self.param_window.destroy()
        self.root.deiconify()


    # 添加控制功能方法
    def start_detection(self):
        """开始检测"""
        try:
            # 获取选中的模型路径
            model_path = self.get_selected_model()
            if not model_path:
                messagebox.showerror("错误", "请选择模型文件")
                return

            # 获取输入路径
            input_path = self.path_entry.get()
            if not input_path:
                messagebox.showerror("错误", "请选择输入文件或文件夹")
                return

            self.text_results.insert(tk.END, "开始检测\n")

            # 初始化预测器
            self.predictor = BatchPredictor(model_path)

            # 连接到UI应用（作为服务器）
            if self.predictor.connect_to_ui():
                self.text_results.insert(tk.END, "预测器已连接到UI应用\n")

            # 根据输入类型执行检测
            if os.path.isfile(input_path):
                # 单个文件或文件列表
                if input_path.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff')):
                    # 单张图片检测
                    self.detect_single_image(input_path)
                elif input_path.lower().endswith(('.txt',)):
                    # 多图片检测 - 从文本文件读取图片路径列表
                    self.detect_multiple_images_from_file(input_path)
                else:
                    # 视频文件处理
                    self.detect_video(input_path)
            elif os.path.isdir(input_path):
                # 文件夹处理
                self.detect_folder(input_path)
            elif input_path == "摄像头":
                # 摄像头检测
                self.detect_camera()
            elif ";" in input_path or "," in input_path:
                # 多个文件路径（通过分号或逗号分隔）
                file_paths = [path.strip() for path in input_path.replace(",", ";").split(";") if path.strip()]
                valid_image_paths = []
                for path in file_paths:
                    if os.path.isfile(path) and path.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff')):
                        valid_image_paths.append(path)

                if valid_image_paths:
                    self.text_results.insert(tk.END, f"开始批量检测 {len(valid_image_paths)} 张图片...\n")
                    self.detect_batch_files(valid_image_paths)
                else:
                    messagebox.showerror("错误", "请选择有效的图片文件")
                    return
            else:
                # 其他情况，可能是无效路径
                messagebox.showerror("错误", "不支持的输入类型")
                return

        except Exception as e:
            error_msg = f"检测过程中出错: {str(e)}\n"
            self.text_results.insert(tk.END, error_msg)
            messagebox.showerror("错误", error_msg)



    def detect_multiple_images_from_file(self, txt_file_path):
        """从文本文件中读取图片路径列表并进行批量检测"""
        try:
            # 读取文本文件中的图片路径
            with open(txt_file_path, 'r', encoding='utf-8') as f:
                image_paths = [line.strip() for line in f if line.strip() and not line.startswith('#')]

            if not image_paths:
                messagebox.showerror("错误", "文本文件中没有有效的图片路径")
                return

            self.text_results.insert(tk.END, f"开始批量检测 {len(image_paths)} 张图片...\n")

            # 使用已有的批量检测方法
            self.detect_batch_files(image_paths)

        except Exception as e:
            error_msg = f"读取图片列表文件时出错: {str(e)}\n"
            self.text_results.insert(tk.END, error_msg)
            messagebox.showerror("错误", error_msg)


    # 在 MainWindow 类中添加以下方法

    def detect_batch_files(self, file_paths):
        """检测批量文件"""
        try:
            # 获取选中的模型路径
            model_path = self.get_selected_model()
            if not model_path:
                messagebox.showerror("错误", "请选择模型文件")
                return

            if not file_paths:
                messagebox.showerror("错误", "请选择要检测的图片文件")
                return

            self.text_results.insert(tk.END, f"开始批量检测 {len(file_paths)} 张图片...\n")

            def run_detection():
                try:
                    # 初始化预测器
                    self.predictor = BatchPredictor(model_path)

                    # 连接到UI应用（作为服务器）
                    if self.predictor.connect_to_ui():
                        self.text_results.insert(tk.END, "预测器已连接到UI应用\n")

                    results = self.predictor.predict_batch_with_progress(file_paths)
                    # 结果会通过Socket自动显示在UI上
                except Exception as e:
                    self.text_results.insert(tk.END, f"批量检测出错: {str(e)}\n")

            # 在后台线程中运行检测
            detection_thread = threading.Thread(target=run_detection)
            detection_thread.daemon = True
            detection_thread.start()

        except Exception as e:
            error_msg = f"批量检测过程中出错: {str(e)}\n"
            self.text_results.insert(tk.END, error_msg)
            messagebox.showerror("错误", error_msg)


    def detect_folder(self, folder_path):
        """检测文件夹"""

        def run_detection():
            try:
                results = self.predictor.predict_folder(folder_path)
                self.process_detection_results(results)
            except Exception as e:
                self.text_results.insert(tk.END, f"检测出错: {str(e)}\n")

        # 在后台线程中运行检测
        detection_thread = threading.Thread(target=run_detection)
        detection_thread.daemon = True
        detection_thread.start()

# 在 MainWindow 类中添加新的检测方法

    def detect_single_image(self, image_path):
        """检测单张图片"""

        def run_detection():
            try:
                # 初始化预测器
                model_path = self.get_selected_model()
                if not model_path:
                    messagebox.showerror("错误", "请选择模型文件")
                    return

                self.predictor = BatchPredictor(model_path)

                # 连接到UI应用（作为服务器）
                if self.predictor.connect_to_ui():
                    self.text_results.insert(tk.END, "预测器已连接到UI应用\n")

                result = self.predictor.predict_single_image(image_path)
                # 结果会通过Socket自动显示在UI上
            except Exception as e:
                self.text_results.insert(tk.END, f"检测出错: {str(e)}\n")

        # 在后台线程中运行检测
        detection_thread = threading.Thread(target=run_detection)
        detection_thread.daemon = True
        detection_thread.start()


    def detect_video(self, video_path):
        """检测视频"""
        # 保存原始视频路径
        self.original_video_path = video_path
        self.video_detection_running = True  # 设置检测状态

        def run_detection():
            try:
                # 初始化预测器
                model_path = self.get_selected_model()
                if not model_path:
                    messagebox.showerror("错误", "请选择模型文件")
                    return

                self.predictor = BatchPredictor(model_path)

                # 连接到UI应用（作为服务器）
                if self.predictor.connect_to_ui():
                    self.text_results.insert(tk.END, "预测器已连接到UI应用\n")

                result = self.predictor.predict_video(video_path)
                # 结果会通过Socket自动显示在UI上
            except Exception as e:
                self.text_results.insert(tk.END, f"检测出错: {str(e)}\n")
            finally:
                # 确保在检测结束后更新UI状态
                self.root.after(0, self.on_video_detection_finished)
                self.video_detection_running = False  # 重置检测状态

        # 在后台线程中运行检测
        detection_thread = threading.Thread(target=run_detection)
        detection_thread.daemon = True
        detection_thread.start()



    def on_video_detection_finished(self):
        """视频检测完成后的处理"""
        # 重置状态
        self.video_paused = False
        # 确保界面可以响应
        self.text_results.see(tk.END)
        self.text_results.update_idletasks()

    def detect_camera(self):
        """检测摄像头"""
        try:
            model_path = self.get_selected_model()
            if not model_path:
                messagebox.showerror("错误", "请选择模型文件")
                return

            self.predictor = BatchPredictor(model_path)

            if self.predictor.connect_to_ui():
                self.text_results.insert(tk.END, "预测器已连接到UI应用\n")

            # 启动检测线程（非阻塞）
            detection_thread = threading.Thread(target=self.predictor.predict_camera)
            detection_thread.daemon = True
            detection_thread.start()

            # 继续播放摄像头（保持原始画面）
            self.camera_running = True
            self.update_camera_frame()  # 开始定时刷新原始画面

        except Exception as e:
            self.text_results.insert(tk.END, f"检测出错: {str(e)}\n")



    def update_ui_with_result(self, result_data):
        """在UI中更新检测结果"""
        try:
            msg_type = result_data.get('type', '')

            # 处理进度信息
            if msg_type == 'progress':
                progress_text = f"进度: {result_data['percentage']:.1f}% ({result_data['current']}/{result_data['total']})\n"
                self.text_results.insert(tk.END, progress_text)
                self.text_results.see(tk.END)
                self.text_results.update_idletasks()
                return

            # 处理进度总结信息
            if msg_type == 'progress_summary':
                summary_text = f"\n检测完成: 成功 {result_data['success']} 张, 失败 {result_data['failed']} 张\n"
                self.text_results.insert(tk.END, summary_text)
                self.text_results.see(tk.END)
                self.text_results.update_idletasks()

                # 批处理完成后初始化批处理结果并显示翻页按钮
                self.batch_results = result_data.get('results', [])
                if self.batch_results:
                    self.current_result_index = 0
                    self.create_navigation_buttons()  # 创建翻页按钮
                    self.update_navigation_buttons()
                    # 显示第一个结果
                    self.display_current_result()
                else:
                    self.hide_navigation_buttons()  # 隐藏翻页按钮
                return

            # 处理检测停止消息
            if msg_type == 'detection_stopped':
                message = result_data.get('message', '检测已停止')
                self.text_results.insert(tk.END, f"\n⏹️ {message}\n")
                self.text_results.see(tk.END)
                self.text_results.update_idletasks()
                self.hide_navigation_buttons()  # 隐藏翻页按钮
                return

            # 处理视频开始消息
            if msg_type == 'video_start':
                video_path = result_data.get('video_path', '未知视频')
                self.text_results.insert(tk.END, f"开始处理视频: {video_path}\n")
                self.text_results.see(tk.END)
                self.text_results.update_idletasks()

                # 播放原始视频在左侧画布
                if self.original_video_path and os.path.exists(self.original_video_path):
                    self.play_video(self.original_video_path, canvas_target='original')
                self.hide_navigation_buttons()  # 隐藏翻页按钮
                return

            # 处理视频帧消息
            if msg_type == 'video_frame':
                if result_data.get('status') == 'success':
                    frame_num = result_data.get('frame_number', 0)
                    total_objects = result_data.get('total_objects', 0)
                    self.text_results.insert(tk.END, f"视频帧 {frame_num}: 检测到 {total_objects} 个目标\n")

                    # 显示各类别数量
                    class_counts = result_data.get('class_counts', {})
                    if class_counts:
                        for class_name, count in class_counts.items():
                            self.text_results.insert(tk.END, f"  {class_name}: {count}\n")

                    # 显示检测结果图片到右侧画布
                    result_image_path = result_data.get('result_image_path')
                    if result_image_path and os.path.exists(result_image_path):
                        self.display_result_image(result_image_path)
                else:
                    frame_num = result_data.get('frame_number', 0)
                    error_msg = result_data.get('error', '未知错误')
                    # 如果错误信息太长，只显示前200个字符
                    if len(error_msg) > 200:
                        error_msg = error_msg[:200] + "..."
                    self.text_results.insert(tk.END, f"视频帧 {frame_num} 处理失败: {error_msg}\n")

                self.text_results.see(tk.END)
                self.text_results.update_idletasks()
                self.hide_navigation_buttons()  # 隐藏翻页按钮
                return

            # 处理视频结束消息
                        # 处理视频结束消息
                        # 处理视频结束消息
            if msg_type == 'video_end':
                total_frames = result_data.get('total_frames', 0)
                processed_frames = result_data.get('processed_frames', 0)  # 获取实际处理的帧数
                success_frames = result_data.get('success_frames', 0)
                failed_frames = result_data.get('failed_frames', 0)
                frame_skip = result_data.get('frame_skip', 1)  # 获取跳帧信息

                self.text_results.insert(tk.END, f"\n视频处理完成:\n")
                self.text_results.insert(tk.END, f"  总帧数: {total_frames}\n")
                self.text_results.insert(tk.END, f"  处理帧数: {processed_frames} (跳帧率: 1/{frame_skip})\n")
                self.text_results.insert(tk.END, f"  成功帧数: {success_frames}\n")
                self.text_results.insert(tk.END, f"  失败帧数: {failed_frames}\n")
                self.text_results.see(tk.END)
                self.text_results.update_idletasks()

                # 显示处理完成的视频信息
                result_video_path = result_data.get('result_video_path')
                if result_video_path:
                    if os.path.exists(result_video_path):
                        self.text_results.insert(tk.END, f"检测完成的视频已保存至: {result_video_path}\n")
                    else:
                        # 如果指定路径不存在，尝试修复路径
                        fixed_path = self.fix_video_path(result_video_path)
                        if os.path.exists(fixed_path):
                            self.text_results.insert(tk.END, f"检测完成的视频已保存至: {fixed_path}\n")
                        else:
                            self.text_results.insert(tk.END, f"警告: 预期的视频文件不存在: {result_video_path}\n")
                            # 尝试查找可能的文件
                            self.search_for_video_file(result_video_path)
                self.hide_navigation_buttons()  # 隐藏翻页按钮
                return


            # 处理视频完成消息 - 自动播放视频
                        # 处理视频完成消息 - 自动播放视频
            # 处理视频完成消息 - 自动播放视频
            if msg_type == 'video_finished':
                video_path = result_data.get('video_path')
                if video_path:
                    # 检查文件是否存在，如果不存在尝试修复路径
                    if not os.path.exists(video_path):
                        # 尝试修复路径
                        video_path = self.fix_video_path(video_path)

                    if os.path.exists(video_path):
                        self.text_results.insert(tk.END, f"正在播放检测结果视频: {video_path}\n")
                        # 在右侧画布播放结果视频
                        self.play_video(video_path, canvas_target='detected')

                    else:
                        self.text_results.insert(tk.END, f"错误: 视频文件不存在: {video_path}\n")
                else:
                    self.text_results.insert(tk.END, "错误: 未收到有效的视频路径\n")

                # 确保界面可以响应
                self.text_results.see(tk.END)
                self.text_results.update_idletasks()
                return
            # 处理视频进度消息
            if msg_type == 'video_progress':
                processed = result_data.get('processed_frames', 0)
                total = result_data.get('total_frames', 0)
                percentage = result_data.get('percentage', 0)

                # 更新进度显示，但不频繁更新UI
                if processed % 10 == 0:  # 每10帧更新一次
                    self.text_results.insert(tk.END, f"视频处理进度: {processed}/{total} 帧 ({percentage:.1f}%)\n")
                    self.text_results.see(tk.END)
                return




            # 处理摄像头开始消息
            if msg_type == 'camera_start':
                self.text_results.insert(tk.END, "开始摄像头实时检测...\n")
                self.text_results.see(tk.END)
                self.text_results.update_idletasks()
                self.hide_navigation_buttons()  # 隐藏翻页按钮
                return

            # 处理摄像头帧消息
                        # 处理摄像头帧消息
            if msg_type == 'camera_frame':
                if result_data.get('status') == 'success':
                    frame_num = result_data.get('frame_number', 0)
                    original_frame_num = result_data.get('original_frame_number', 0)
                    total_objects = result_data.get('total_objects', 0)
                    self.text_results.insert(tk.END,
                                             f"摄像头帧 {frame_num} (原始帧 {original_frame_num}): 检测到 {total_objects} 个目标\n")

                    # 显示各类别数量
                    class_counts = result_data.get('class_counts', {})
                    if class_counts:
                        for class_name, count in class_counts.items():
                            self.text_results.insert(tk.END, f"  {class_name}: {count}\n")

                    # 显示检测结果图片到右侧画布 (canvas_detected)
                    result_image_path = result_data.get('result_image_path')
                    if result_image_path and os.path.exists(result_image_path):
                        self.display_result_image_on_canvas(result_image_path, canvas_target='detected')
                else:
                    frame_num = result_data.get('frame_number', 0)
                    original_frame_num = result_data.get('original_frame_number', 0)
                    error_msg = result_data.get('error', '未知错误')
                    # 如果错误信息太长，只显示前200个字符
                    if len(error_msg) > 200:
                        error_msg = error_msg[:200] + "..."
                    self.text_results.insert(tk.END, f"摄像头帧 {frame_num} (原始帧 {original_frame_num}) 处理失败: {error_msg}\n")

                self.text_results.see(tk.END)
                self.text_results.update_idletasks()
                self.hide_navigation_buttons()  # 隐藏翻页按钮
                return


            # 处理普通检测结果（非批处理）
            if result_data.get('status') == 'success':
                image_path = result_data['image_path']
                total_objects = result_data['total_objects']
                class_counts = result_data['class_counts']

                # 显示结果
                self.text_results.insert(tk.END, f"\n图片: {image_path}\n", "title")
                self.text_results.insert(tk.END, f"总目标数: {total_objects}\n", "highlight")
                self.text_results.insert(tk.END, "各类别数量:\n", "title")
                # 检查是否检测到火灾
                fire_count = 0
                for class_name, count in class_counts.items():
                    self.text_results.insert(tk.END, f"  {class_name}: {count}\n", "class_label")
                    # 检查是否检测到火灾（不区分大小写）
                    if class_name.lower() == 'fire':
                        fire_count = count

                # 如果检测到火灾，添加警告信息
                if fire_count > 0:
                    self.text_results.insert(tk.END, f"⚠️ 警告：发现{fire_count}处火灾！\n", "error")

                    # 弹出警告框提醒用户

                # 显示预测结果图片
                result_image_path = result_data.get('result_image_path')
                if result_image_path and os.path.exists(result_image_path):
                    self.display_result_image(result_image_path)
                else:
                    # 如果没有保存的预测图片，尝试显示原始图片
                    self.display_result_image(image_path)

                self.hide_navigation_buttons()  # 隐藏翻页按钮
            else:
                error_msg = result_data.get('error', '未知错误')
                # 如果错误信息太长，只显示前200个字符
                if len(error_msg) > 200:
                    error_msg = error_msg[:200] + "..."
                self.text_results.insert(tk.END, f"处理失败: {error_msg}\n")
                self.hide_navigation_buttons()  # 隐藏翻页按钮

            self.text_results.see(tk.END)  # 滚动到最新内容
            self.text_results.update_idletasks()

        except Exception as e:
            # 处理update_ui_with_result方法本身的异常
            error_detail = f"更新UI时出错: {str(e)}\n"
            self.text_results.insert(tk.END, error_detail)
            self.text_results.see(tk.END)
            self.text_results.update_idletasks()
            import traceback
            traceback.print_exc()

    def display_result_image_on_canvas(self, image_path, target_canvas):
        """在指定画布上显示结果图像"""
        try:
            # 判断是否为视频文件
            if image_path.lower().endswith(('.mp4', '.avi', '.mov', '.wmv', '.mkv')):
                # 使用 OpenCV 读取视频的第一帧
                cap = cv2.VideoCapture(image_path)
                if not cap.isOpened():
                    raise Exception("无法打开视频文件")

                ret, frame = cap.read()
                cap.release()

                if ret:
                    # 将 OpenCV 图像转换为 PIL 图像
                    frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
                    image = Image.fromarray(frame_rgb)
                    self.display_image_on_canvas(image, target_canvas)
                else:
                    raise Exception("无法读取视频帧")
            else:
                # 处理普通图片
                image = Image.open(image_path)
                self.display_image_on_canvas(image, target_canvas)
        except Exception as e:
            print(f"无法在画布上显示结果图片/视频: {str(e)}")

    def fix_video_path(self, video_path):
        """修复视频路径问题"""
        if os.path.exists(video_path):
            return video_path

        # 如果是相对路径，尝试转换为绝对路径
        if not os.path.isabs(video_path):
            current_dir = os.path.dirname(os.path.abspath(__file__))
            project_root = os.path.dirname(os.path.dirname(current_dir))
            absolute_path = os.path.join(project_root, video_path)
            if os.path.exists(absolute_path):
                return absolute_path

            # 尝试在UI/ui子目录中查找
            ui_ui_path = os.path.join(project_root, 'UI', 'ui', video_path)
            if os.path.exists(ui_ui_path):
                return ui_ui_path

        # 尝试不同的扩展名
        base_name = os.path.splitext(video_path)[0]
        possible_extensions = ['.mp4', '.avi', '.mov', '.mkv', '.wmv']

        # 尝试当前路径的不同扩展名
        for ext in possible_extensions:
            test_path = base_name + ext
            if os.path.exists(test_path):
                return test_path

            # 也尝试绝对路径版本
            if not os.path.isabs(test_path):
                current_dir = os.path.dirname(os.path.abspath(__file__))
                project_root = os.path.dirname(os.path.dirname(current_dir))
                absolute_test_path = os.path.join(project_root, test_path)
                if os.path.exists(absolute_test_path):
                    return absolute_test_path

                # 尝试UI/ui子目录
                ui_ui_test_path = os.path.join(project_root, 'UI', 'ui', test_path)
                if os.path.exists(ui_ui_test_path):
                    return ui_ui_test_path

        return video_path  # 如果都找不到，返回原始路径


    def search_for_video_file(self, expected_path):
        """搜索可能的视频文件"""
        base_name = os.path.splitext(os.path.basename(expected_path))[0]
        possible_extensions = ['.mp4', '.avi', '.mov', '.mkv', '.wmv']

        # 搜索目录
        search_dirs = [
            os.path.dirname(expected_path),
            os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'results'),
            os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'runs', 'predict')
        ]

        for search_dir in search_dirs:
            if os.path.exists(search_dir):
                for ext in possible_extensions:
                    test_path = os.path.join(search_dir, base_name + ext)
                    if os.path.exists(test_path):
                        self.text_results.insert(tk.END, f"提示: 找到可能的视频文件: {test_path}\n")
                        return test_path

        self.text_results.insert(tk.END, f"提示: 在以下目录中未找到匹配的视频文件: {', '.join(search_dirs)}\n")
        return None






    def process_detection_results(self, results):
        """处理检测结果"""
        for result in results:
            if result['status'] == 'success':
                image_path = result['image_path']
                total_objects = result['total_objects']
                class_counts = result['class_counts']

                # 显示结果
                self.text_results.insert(tk.END, f"\n图片: {image_path}\n")
                self.text_results.insert(tk.END, f"总目标数: {total_objects}\n")
                self.text_results.insert(tk.END, "各类别数量:\n")
                for class_name, count in class_counts.items():
                    self.text_results.insert(tk.END, f"  {class_name}: {count}\n")
            else:
                self.text_results.insert(tk.END, f"处理失败: {result.get('error', '未知错误')}\n")

            self.text_results.see(tk.END)  # 滚动到最新内容
            self.text_results.update_idletasks()


    def pause_detection(self):
        """暂停检测"""
        # 如果有正在运行的预测器，向其发送暂停指令
        if self.predictor:
            if hasattr(self.predictor, 'is_paused') and self.predictor.is_paused():
                # 如果已经暂停，则恢复
                self.predictor.resume_detection()
                self.text_results.insert(tk.END, "恢复检测\n")
            else:
                # 否则暂停
                self.predictor.pause_detection()
                self.text_results.insert(tk.END, "暂停检测\n")
        elif self.camera_running and not self.video_paused:
            # 如果正在运行摄像头，则暂停摄像头播放
            self.video_paused = True
            self.text_results.insert(tk.END, "暂停摄像头播放\n")
        elif self.camera_running and self.video_paused:
            # 如果摄像头已经暂停，则恢复
            self.video_paused = False
            self.text_results.insert(tk.END, "恢复摄像头播放\n")
            # 继续播放摄像头
            self.update_camera_frame()
        elif self.video_running and not self.video_paused:
            # 如果正在播放视频，则暂停视频播放
            self.video_paused = True
            self.text_results.insert(tk.END, "暂停视频播放\n")
        elif self.video_running and self.video_paused:
            # 如果视频已经暂停，则恢复
            self.video_paused = False
            self.text_results.insert(tk.END, "恢复视频播放\n")
            # 继续播放视频
            self.update_video_frame()
        else:
            self.text_results.insert(tk.END, "没有正在运行的检测任务或视频播放\n")

        self.text_results.see(tk.END)
        self.text_results.update_idletasks()

    def stop_detection(self):
        """停止检测"""
        self.text_results.insert(tk.END, "停止检测\n")
        # 添加实际的停止检测逻辑
        if self.predictor:
            self.predictor.stop_detection()

        # 如果有正在运行的视频或摄像头，也停止它们
        self.stop_video()
        self.stop_camera()

        # 重置暂停状态
        self.video_paused = False

        # 更新UI状态
        self.text_results.see(tk.END)
        self.text_results.update_idletasks()


    def save_detection(self):
        """保存当前在画布上显示的检测结果图像。"""
        # 检查画布上是否有图像
        if not hasattr(self, 'current_image') or self.current_image is None:
            messagebox.showwarning("提示", "当前没有图像可保存。")
            return

        # 获取当前画布上的PIL Image对象
        pil_image = self.current_image  # 假设 self.current_image 存储的是处理后的PIL Image

        # 如果没有直接的PIL Image引用，可以从PhotoImage重建（但质量可能下降）
        # 通常，更好的做法是在显示图像时就保存PIL Image的引用，如上一行所示。
        # 如果只能从PhotoImage获取，可以这样做（不推荐，因为PhotoImage是位图）：
        # if not pil_image and hasattr(self, 'photo') and self.photo:
        #     # 这种方法不直接，且可能不准确，最好在显示时保存引用。
        #     messagebox.showwarning("提示", "无法获取原始图像数据，保存的图像质量可能降低。")
        #     # 这里需要更复杂的操作从Tkinter.PhotoImage获取数据，通常不推荐。

        # 让用户选择保存文件的路径和名称
        file_path = filedialog.asksaveasfilename(
            title="保存检测结果",
            filetypes=[
                ("PNG files", "*.png"),
                ("JPEG files", "*.jpg;*.jpeg"),
                ("BMP files", "*.bmp"),
                ("All files", "*.*")
            ],
            defaultextension=".png"
        )

        # 如果用户取消了对话框
        if not file_path:
            return

        try:
            # 使用PIL保存图像
            pil_image.save(file_path)
            messagebox.showinfo("保存成功", f"图像已成功保存至:\n{file_path}")
        except Exception as e:
            error_msg = f"保存图像时发生错误: {str(e)}"
            print(error_msg)
            messagebox.showerror("保存失败", error_msg)



    def open_image(self):
        file_paths = filedialog.askopenfilenames(
            title="选择图片文件",
            filetypes=[("图片文件", "*.jpg *.jpeg *.png *.bmp *.tiff")]
        )
        if file_paths:
            # 如果只选择了一个文件，保持原有行为
            if len(file_paths) == 1:
                self.path_entry.delete(0, tk.END)
                self.path_entry.insert(0, file_paths[0])
                self.display_image(file_paths[0])
                self.add_recent_file(file_paths[0])
            else:
                # 如果选择了多个文件，将它们以分号分隔显示在路径框中
                self.path_entry.delete(0, tk.END)
                self.path_entry.insert(0, ";".join(file_paths))
                # 显示第一张图片作为预览
                self.display_image(file_paths[0])

                for file_path in file_paths:
                    self.add_recent_file(file_path)


    def open_video(self, video_path=None):
        if video_path is None:
            file_path = filedialog.askopenfilename(
                title="选择视频文件",
                filetypes=[
                    ("视频文件", "*.mp4 *.avi *.mov *.mkv *.wmv"),
                    ("所有文件", "*.*")
                ]
            )
        else:
            file_path = video_path

        if file_path:
            self.path_entry.delete(0, tk.END)
            self.path_entry.insert(0, file_path)
            self.stop_camera()  # 停止可能正在运行的摄像头
            # 播放在左侧画布（原始视频）
            self.play_video(file_path, canvas_target='original')
            self.add_recent_file(file_path)

    def open_camera(self):
        self.path_entry.delete(0, tk.END)
        self.path_entry.insert(0, "摄像头")
        self.stop_video()  # 停止可能正在播放的视频
        self.start_camera()

    def browse_file(self):
        file_path = filedialog.askopenfilename(
            title="选择文件",
            filetypes=[
                ("所有支持文件", "*.jpg *.jpeg *.png *.bmp *.tiff *.mp4 *.avi *.mov *.mkv *.wmv"),
                ("图片文件", "*.jpg *.jpeg *.png *.bmp *.tiff"),
                ("视频文件", "*.mp4 *.avi *.mov *.mkv *.wmv")
            ]
        )
        if file_path:
            self.path_entry.delete(0, tk.END)
            self.path_entry.insert(0, file_path)
            # 根据文件扩展名判断类型
            if file_path.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff')):
                self.display_image(file_path)
                self.add_recent_file(file_path)
            else:
                self.stop_camera()
                # 播放在左侧画布（原始视频）
                self.play_video(file_path, canvas_target='original')

    def display_image(self, image_path):
        """在左侧画布显示图片"""
        try:
            # 停止可能正在运行的视频或摄像头
            self.stop_video()
            self.stop_camera()

            # 加载并显示图片
            image = Image.open(image_path)
            self.display_image_on_canvas(image, self.canvas_original)
            self.text_results.insert(tk.END, f"已加载图片: {image_path}\n")
            self.current_image = image.copy()
        except Exception as e:
            messagebox.showerror("错误", f"无法加载图片: {str(e)}")

    def display_image_on_canvas(self, image, canvas):
        """在指定画布上显示图片"""
        # 清空画布
        canvas.delete("all")

        # 调整图片大小以适应画布
        canvas_width = canvas.winfo_width()
        canvas_height = canvas.winfo_height()

        # 如果画布大小为1，则使用默认大小
        if canvas_width <= 1:
            canvas_width = 300
        if canvas_height <= 1:
            canvas_height = 300

        # 按比例缩放图片
        image_ratio = image.width / image.height
        canvas_ratio = canvas_width / canvas_height

        if image_ratio > canvas_ratio:
            # 图片较宽，以宽度为准
            new_width = canvas_width
            new_height = int(canvas_width / image_ratio)
        else:
            # 图片较高，以高度为准
            new_height = canvas_height
            new_width = int(canvas_height * image_ratio)

        image = image.resize((new_width, new_height), Image.LANCZOS)

        # 转换为PhotoImage
        photo = ImageTk.PhotoImage(image)

        # 在画布中央显示图片
        canvas.create_image(canvas_width//2, canvas_height//2, image=photo)

        # 保存引用以防被垃圾回收
        canvas.image = photo


        # 修改 play_video 方法，添加一个参数来指定在哪个画布上播放
    def play_video(self, video_path, canvas_target='original'):
        """播放视频"""
        self.stop_video()
        self.stop_camera()

        if not os.path.exists(video_path):
            messagebox.showerror("错误", f"视频文件不存在: {video_path}")
            return

        # 尝试多种后端打开视频
        backends = [cv2.CAP_FFMPEG, cv2.CAP_DSHOW, cv2.CAP_MSMF, cv2.CAP_ANY]
        self.cap = None

        for backend in backends:
            try:
                self.cap = cv2.VideoCapture(video_path, backend)
                if self.cap.isOpened():
                    print(f"使用后端 {backend} 成功打开视频")
                    break
            except Exception as e:
                print(f"使用后端 {backend} 打开视频失败: {e}")
                continue

        if not self.cap or not self.cap.isOpened():
            messagebox.showerror("错误", f"无法打开视频文件: {video_path}\n可能不支持该格式或缺少编解码器")
            return

        # 获取视频信息
        fps = self.cap.get(cv2.CAP_PROP_FPS)
        width = int(self.cap.get(cv2.CAP_PROP_FRAME_WIDTH))
        height = int(self.cap.get(cv2.CAP_PROP_FRAME_HEIGHT))

        print(f"视频信息: {width}x{height} @ {fps} FPS")

        self.video_running = True
        self.video_paused = False  # 重置暂停状态

        # 根据目标画布设置标签和画布
        if canvas_target == 'detected':
            target_canvas = self.canvas_detected
            target_label = self.canvas_detected_label
            target_label.config(text=f"检测结果: {os.path.basename(video_path)}")
        else:
            target_canvas = self.canvas_original
            target_label = self.canvas_original_label
            target_label.config(text=f"原始文件: {os.path.basename(video_path)}")
            print("原始视频信息:", video_path)

        self.text_results.insert(tk.END, f"开始播放视频: {video_path}\n")
        if fps > 0:
            self.text_results.insert(tk.END, f"视频信息: {width}x{height} @ {fps:.2f} FPS\n")
        self.text_results.see(tk.END)
        self.text_results.update_idletasks()
        self.update_video_frame(target_canvas)  # 传递目标画布参数





    def update_video_frame(self, target_canvas=None):
        """更新视频帧"""
        if target_canvas is None:
            target_canvas = self.canvas_original  # 默认使用左侧画布以保持向后兼容

        # 检查是否暂停
        if self.video_paused:
            # 如果暂停，等待一段时间后再次检查
            self.root.after(100, lambda: self.update_video_frame(target_canvas))
            return

        if not self.video_running or not self.cap or not self.cap.isOpened():
            return

        ret, frame = self.cap.read()
        if ret:
            # 转换颜色空间 (BGR to RGB)
            frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            image = Image.fromarray(frame_rgb)
            self.display_image_on_canvas(image, target_canvas)  # 使用指定画布

            # 根据视频帧率调整更新间隔
            fps = self.cap.get(cv2.CAP_PROP_FPS)
            if fps > 0:
                delay = int(1000 / fps)
            else:
                delay = 30  # 默认33ms (约30fps)

            # 继续播放下一帧
            self.root.after(delay, lambda: self.update_video_frame(target_canvas))
        else:
            # 视频播放结束
            self.stop_video()
            self.text_results.insert(tk.END, "视频播放结束\n")
            self.text_results.see(tk.END)
            self.text_results.update_idletasks()








    def start_camera(self):
        """启动摄像头"""
        self.stop_video()
        self.stop_camera()

        # 尝试多种后端打开摄像头
        backends = [cv2.CAP_DSHOW, cv2.CAP_MSMF, cv2.CAP_ANY]
        self.cap = None

        for backend in backends:
            try:
                self.cap = cv2.VideoCapture(0, backend)
                if self.cap.isOpened():
                    # 设置摄像头参数以减少自动调整
                    self.cap.set(cv2.CAP_PROP_FRAME_WIDTH, 640)
                    self.cap.set(cv2.CAP_PROP_FRAME_HEIGHT, 480)
                    self.cap.set(cv2.CAP_PROP_FPS, 30)

                    # 固定曝光和白平衡参数，减少自动调整
                    self.cap.set(cv2.CAP_PROP_AUTO_EXPOSURE, 0)  # 禁用自动曝光
                    self.cap.set(cv2.CAP_PROP_EXPOSURE, -6)      # 手动设置曝光值
                    self.cap.set(cv2.CAP_PROP_AUTO_WB, 0)        # 禁用自动白平衡（如果支持）

                    # 测试是否能读取帧
                    ret, frame = self.cap.read()
                    if ret:
                        print(f"摄像头已启动，使用后端: {backend}")
                        break
                    else:
                        self.cap.release()
                        self.cap = None
            except Exception as e:
                print(f"使用后端 {backend} 启动摄像头失败: {e}")
                if self.cap:
                    self.cap.release()
                    self.cap = None

        if not self.cap or not self.cap.isOpened():
            messagebox.showerror("错误", "无法打开摄像头")
            return

        self.camera_running = True
        self.text_results.insert(tk.END, "摄像头已启动\n")
        # 确保摄像头视频在左侧画布播放
        self.update_camera_frame()



    def update_camera_frame(self):
        """更新摄像头帧"""
        if not self.camera_running or self.video_paused:
            return

        ret, frame = self.cap.read()
        if ret:
            frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
            image = Image.fromarray(frame_rgb)
            self.display_image_on_canvas(image, self.canvas_original)
        self.root.after(33, self.update_camera_frame)  # ~30fps






    def stop_video(self):
        """停止视频播放"""
        self.video_running = False
        self.video_paused = False  # 重置暂停状态
        if self.cap and self.cap.isOpened():
            self.cap.release()
            self.cap = None


    def stop_camera(self):
        """停止摄像头"""
        self.camera_running = False
        if self.cap and self.cap.isOpened():
            self.cap.release()
            self.cap = None


    def on_mouse_wheel(self, event):
        if event.delta > 0:
            self.canvas_original.scale("all", event.x, event.y, 1.1, 1.1)
            self.canvas_detected.scale("all", event.x, event.y, 1.1, 1.1)
        else:
            self.canvas_original.scale("all", event.x, event.y, 0.9, 0.9)
            self.canvas_detected.scale("all", event.x, event.y, 0.9, 0.9)

    def __del__(self):
        # 确保资源释放
        self.stop_video()
        self.stop_camera()


    def on_mouse_wheel_original(self, event):
        """处理原始图像画布的鼠标滚轮事件 - 实现图片缩放（Windows系统）"""
        # Windows系统使用event.delta
        if event.delta > 0:
            # 向上滚动，放大
            scale_factor = 1.1
        else:
            # 向下滚动，缩小
            scale_factor = 0.9

        # 获取鼠标位置作为缩放中心
        x = self.canvas_original.canvasx(event.x)
        y = self.canvas_original.canvasy(event.y)

        # 执行缩放
        self.canvas_original.scale("all", x, y, scale_factor, scale_factor)

    def on_mouse_wheel_detected(self, event):
        """处理检测结果画布的鼠标滚轮事件 - 实现图片缩放（Windows系统）"""
        # Windows系统使用event.delta
        if event.delta > 0:
            # 向上滚动，放大
            scale_factor = 1.1
        else:
            # 向下滚动，缩小
            scale_factor = 0.9

        # 获取鼠标位置作为缩放中心
        x = self.canvas_detected.canvasx(event.x)
        y = self.canvas_detected.canvasy(event.y)

        # 执行缩放
        self.canvas_detected.scale("all", x, y, scale_factor, scale_factor)


    # --- 新增：加载最近文件 ---
    def load_recent_files(self):
        """
        从 JSON 文件加载最近文件列表。
        """
        try:
            if os.path.exists(self.recent_files_path):
                with open(self.recent_files_path, 'r', encoding='utf-8') as f:
                    data = json.load(f)
                    # 确保加载的是列表
                    if isinstance(data, list):
                        # 过滤掉不存在的文件路径
                        self.recent_files = [path for path in data if os.path.exists(path)]
                        # 重新保存以清理不存在的文件
                        self.save_recent_files()
        except Exception as e:
            print(f"加载最近文件失败: {e}")
            self.recent_files = [] # 加载失败则使用空列表

    # --- 新增：保存最近文件 ---
    def save_recent_files(self):
        """
        将最近文件列表保存到 JSON 文件。
        """
        try:
            with open(self.recent_files_path, 'w', encoding='utf-8') as f:
                json.dump(self.recent_files, f, ensure_ascii=False, indent=2)
        except Exception as e:
            print(f"保存最近文件失败: {e}")

    # --- 新增：窗口关闭事件处理 ---
    def on_closing(self):
        """
        窗口关闭时的处理。
        """
        # 保存最近文件
        self.save_recent_files()
        # 其他清理工作...
        self.root.destroy()

    def create_recent_files_menu(self):
        """
        在菜单栏的 '文件' 菜单下创建 '最近文件' 子菜单。
        """
        # 假设 MenuBar 有一个名为 'file_menu' 的属性，指向 '文件' 菜单
        # 这需要你在 MenuBar 类中实现
        if hasattr(self.menu_bar, 'file_menu'):
            # 创建一个分隔符
            self.menu_bar.file_menu.add_separator()
            # 创建 '最近文件' 子菜单
            self.recent_files_menu = tk.Menu(self.menu_bar.file_menu, tearoff=0)
            self.menu_bar.file_menu.add_cascade(label="最近文件", menu=self.recent_files_menu)
            # 更新菜单项
            self.update_recent_files_menu()

    def update_recent_files_menu(self):
        """
        根据 self.recent_files 列表更新 '最近文件' 菜单中的选项。
        """
        # 清空现有菜单项
        self.recent_files_menu.delete(0, tk.END)

        if not self.recent_files:
            # 如果没有最近文件，显示一个禁用的提示项
            self.recent_files_menu.add_command(label="无最近文件", state=tk.DISABLED)
        else:
            # 添加最近文件项
            for i, file_path in enumerate(self.recent_files):
                # 获取文件名（更简洁）
                file_name = os.path.basename(file_path)
                # 为了处理长文件名，可以截断
                display_name = file_name if len(file_name) <= 30 else file_name[:27] + "..."
                # 创建菜单项，使用 lambda 传入文件路径
                self.recent_files_menu.add_command(
                    label=f"{i+1} {display_name}",
                    command=lambda path=file_path: self.open_recent_file(path)
                )

    def open_recent_file(self, file_path):
        """
        打开指定的最近文件。
        这个方法会被最近文件菜单项的命令调用。
        """
        if not os.path.exists(file_path):
            # 文件可能已被移动或删除
            messagebox.showwarning("文件不存在", f"文件已不存在:\n{file_path}\n将从最近列表中移除。")
            self.remove_recent_file(file_path)
            return

        # 将文件路径设置到路径输入框
        self.path_entry.delete(0, tk.END)
        self.path_entry.insert(0, file_path)

        if file_path.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.tiff')):
            self.display_image(file_path)
        elif file_path.lower().endswith(('.mp4', '.avi', '.mov', '.mkv')):
            self.stop_camera()
            self.play_video(file_path)

    def add_recent_file(self, file_path):
        """
        将一个文件路径添加到最近文件列表。
        如果文件已在列表中，则移到最前面。
        """
        if not os.path.exists(file_path):
            return  # 不添加不存在的文件

        # 规范化路径，避免大小写或斜杠问题导致重复
        norm_path = os.path.normpath(file_path)

        # 如果路径已在列表中，先移除
        if norm_path in self.recent_files:
            self.recent_files.remove(norm_path)
        # 将新路径插入到最前面（最新）
        self.recent_files.insert(0, norm_path)

        # 限制列表长度
        if len(self.recent_files) > self.max_recent_files:
            self.recent_files = self.recent_files[:self.max_recent_files]

        # 保存到文件
        self.save_recent_files()
        # 更新菜单显示
        self.update_recent_files_menu()

    # --- 新增方法：移除最近文件 ---
    def remove_recent_file(self, file_path):
        """
        从最近文件列表中移除指定路径。
        """
        norm_path = os.path.normpath(file_path)
        if norm_path in self.recent_files:
            self.recent_files.remove(norm_path)
            self.save_recent_files()
            self.update_recent_files_menu()  

def main():
    root = tk.Tk()
    app = MainWindow(root)
    root.mainloop()

if __name__ == "__main__":
    main()
